﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;

namespace InstantSpine.Core
{
    [DataContract]
    public sealed class SpineAnatomy : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Number of vertebral body definitions required for ParaSpine
        /// </summary>
        public const int VBCount = 18;

        /// <summary>
        /// Rules to use to compute dependent anatomy parameters
        /// </summary>
        Action<VertebraDiscCompound> ParameterComputer { get; set; }

        PythonScriptCompiler ScriptCompiler { get; set; }

        private string scriptFileName;
        /// <summary>
        /// The name of a Python script file that contains a function "update_parameters"
        /// that updates the anatomy parameters of a provided VertebraDiscAnatomy instance
        /// </summary>
        [DataMember]
        public string ScriptFileName {
            get { return scriptFileName; }
            set {
                if (value != scriptFileName) {
                    scriptFileName = value;
                    PropertyChanged.Raise(this);
                }
            }
        }

        private bool useScript;
        /// <summary>
        /// Determines whether to automatically compute
        /// parameters given ParameterComputer
        /// </summary>
        [DataMember]
        public bool UseScript {
            get { return useScript; }
            set {
                if (value != useScript) {
                    useScript = value;
                    PropertyChanged.Raise(this);
                }
            }
        }

        /// <summary>
        /// get a list of anatomical parameters for each vertebra and its subsequent (caudal/inferior) disc
        /// </summary>        
        [DataMember]
        public ObservableCollection<VertebraDiscCompound> VertebraeAndDiscs { get; private set; }

        /// <summary>
        /// Create "empty" spine model (i.e. no vertebrae defined, default "null" materials)
        /// </summary>
        public SpineAnatomy() {
            this.UseScript = false;
            this.ScriptFileName = "";
            this.ParameterComputer = null;
            this.VertebraeAndDiscs = new ObservableCollection<VertebraDiscCompound>();
            this.ScriptCompiler = new PythonScriptCompiler();
        }

        public void AddDefaultAnatomyData() {
            // add 18 "dummy"/empty VBs
            foreach (var id in Enumerable.Range(0, 18)) {
                VertebraeAndDiscs.Add(new VertebraDiscCompound(id, ParameterComputer));
            }
        }

        public void LoadAndCompileScript(string scriptFileName, Action<string> setCompilationStatusMsg) {
            ParameterComputer = ScriptCompiler
                                .LoadAndCompile<Action<VertebraDiscCompound>>(scriptFileName, "update_parameters", setCompilationStatusMsg);
            ScriptFileName = scriptFileName;
            EnableParameterComputation();
        }

        /// <summary>
        /// Switch on automatic parameter computation for all
        /// vertebral bodies
        /// </summary>
        public void EnableParameterComputation() {
            UseScript = true;
            foreach (var vb in VertebraeAndDiscs) {
                vb.RefreshParameters = ParameterComputer;
                vb.AutoUpdateParameters = true;                
            }            
        }

        /// <summary>
        /// Switch of automatic parameter computation for all
        /// vertebral bodies
        /// </summary>
        public void DisableParameterComputation() {
            UseScript = false;
            foreach (var vb in VertebraeAndDiscs) {
                vb.AutoUpdateParameters = false;
            }
        }
        
        /// <summary>
        /// Export anatomy data to a CSV file
        /// </summary>
        /// <param name="filename">File name of the generated CSV file</param>
        public void SaveAsCSV(string filename) {            
            CSVTools.Save(
                header:    VertebraeAndDiscs[0].ParameterNames,
                rows:      VertebraeAndDiscs.Select(v => v.ParameterValues), 
                filename:  filename);
        }

        /// <summary>
        /// Loads anatomy data from a CSV file
        /// </summary>
        /// <param name="filename">File name of the CSV file containing the anatomy data for each VB-disc compound</param>
        public void LoadFromCSV(string filename) {
            var rawLines = CSVTools.Load(filename);
            var vbIdx = 0;
            foreach (var line in rawLines) {
                VertebraeAndDiscs[vbIdx].InitParameters(
                    line.Select(s => double.Parse(s)).ToList());
                ++vbIdx;
            }
        }
    }
}